<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TEST</title>
</head>

<body>
    <script>
        // @ts-nocheck


        // 用一个全局变量存储被注册的副作用函数
        let activeEffect
        // effect栈
        const effectStack = []
        function cleanup(effectFn) {
            for (let i = 0; i < effectFn.deps.length; i++) {
                const deps = effectFn.deps[i]
                // deps是个set集合
                deps.delete(effectFn)
            }
            effectFn.deps.length = 0
        }
        // 2. 第二步执行effectFn。
        function effect(fn) {
            // 定义这个函数是为了给其价格deps属性，用来存储所有包含当前副作用函数的依赖集合
            // deps作用是每次副作用函数执行时先把它从所有与之关联的依赖集合set中删除防止分支切换会出现
            // 遗留的副作用函数被执行的情况
            const effectFn = () => {
                // 调用cleanup函数完成清除工作
                cleanup(effectFn)
                // 当effectFn执行时，将其设置为当前激活的副作用函数
                activeEffect = effectFn
                // 在调用副作用函数之前将当前副作用函数压入栈中，用栈主要是副作用函数嵌套副作用函数的情况
                effectStack.push(effectFn)
                fn()
                // 在当前副作用函数执行完毕后，将当前副作用函数弹出栈，并把activeEffect还原为之前的值
                effectStack.pop()
                activeEffect = effectStack[effectStack.length - 1]
            }
            //activeEffect.deps用来存储所有与该副作用函数相关联的依赖集合
            effectFn.deps = []
            //执行副作用函数
            effectFn()
        }


        // 存储副作用函数
        const bucket = new WeakMap()
        // 原始数据
        const data = { text: 'hello', name: 'zzd', age: 18 }
        // 3. 第三步，对原始数据的代理，触发副作用函数会触发代理对象的get、set。
        const obj = new Proxy(data, {
            get(target, key, receiver) {
                track(target, key)
                return target[key]
            },
            set(target, key, newVal, receiver) {
                target[key] = newVal
                trigger(target, key)
            },
        })
        // 4. 第四步，get中收集依赖，set中改变数据并重新执行属性对应的副作用函数
        function track(target, key) {
            // 没有activeEffect直接return
            if (!activeEffect) return target[key]
            // 根据target从WeakMap中取得depsMap，他也是一个Map类型
            let depsMap = bucket.get(target)
            // 如果不存在depsMap，那么新建一个Map并与target关联
            if (!depsMap) {
                bucket.set(target, (depsMap = new Map()))
            }
            // 再根据key从depsMap中取得deps，它是一个Set类型
            // 里面存储着所有与当前key关联的副作用函数：effects
            let deps = depsMap.get(key)
            // 如果deps不存在，同样新建一个set并与key关联
            if (!deps) {
                depsMap.set(key, (deps = new Set()))
            }
            // 最后将当前激活的副作用函数放入deps
            deps.add(activeEffect)
            //deps就是一个与当前副作用函数存在联系的依赖集合
            //将其添加到activeEffect.deps数组中
            activeEffect.deps.push(deps) //新增
        }
        function trigger(target, key) {
            //根据target从bucket中取得depsMap，它是key-->effects
            const depsMap = bucket.get(target)
            if (!depsMap) return
            //根据key取得所有副作用函数effects
            const effects = depsMap.get(key)
            //执行副作用函数
            // effects && effects.forEach(fn => fn())
            const effectsToRun = new Set(effects)
            // 避免无限递归
            effects &&
                effects.forEach((effectFn) => {
                    // 如果trigger触发执行的的副作用函数与当前正在执行的副作用函数相同，则不触发执行
                    if (effectFn !== activeEffect) {
                        effectsToRun.add(effectFn)
                    }
                })
            effectsToRun.forEach((effectFn) => effectFn())
        }

        // 1. 第一步调用effect()，传入副作用函数。
        effect(() => {
            let a = obj.text,
                b = obj.name,
                c = obj.age
            console.log('副作用函数执行了')
        })
        setTimeout(() => {
            obj.text = 'dsg'
            obj.name = 'zzd2'
            obj.age = 20
        }, 1000)
    </script>
</body>

</html>